#include "../include/kasm.h"
#include "../include/defs.h"
#include "../include/grubInfo.h"
#include "../include/kc.h"
#include "../include/shmem.h"
#include "../include/shell.h"
#include "../include/sleep.h"
#include "../include/keyboard.h"
#include "../include/exceptions.h"

#define NTERMS      6

/* Se definen las interrupciones */
#define IRQ0	0xFE
#define IRQ1	0xFD
#define IRQ3	0xF7
#define IRQ4	0xEF

#define EXEC_CODE   1
#define KILL_CODE   2
#define RENICE_CODE 3
#define TOP_CODE    4
#define WRITE_CODE  5
#define GCNB_CODE   6
#define GC_CODE     7
#define READ_CODE   8
#define MALLOC_CODE 9
#define SHMGET_CODE 10
#define SHMAT_CODE  11
#define LOCK_CODE   12
#define UNLOCK_CODE 13

//test
DESCR_INT idt[0x95];
IDTR idtr;                      /* IDTR */

int tickpos = 0;

unsigned long *page_directory = (unsigned long *) 0x400000;
unsigned long *page_table = (unsigned long *) 0x401000;
// the page table comes right after the page directory

PROCESO procesos[MAXPROCESOS];
PROCESO init;
peticion peticionesDormir[MAXPROCESOS];
TTY terminales[NTERMS];
unsigned char *keyboardBuffer = (char *) 0;
unsigned char *writeBuffer = (char *) 0;
unsigned char *readBuffer = (char *) 0;

char *backup;
int maxmem;

int terminalActual = 0;
int pidActual = 0;
int proxPid = 1;
int maxmem;

int godswork(int argc, char **argv)
{
    while(1) {
        asm volatile ("hlt");
    }
}

void
int_08 () {
    _Sti ();
}

void
int_81 (char *nombre, int (*proceso) (int argc, char **argv),
        int enBackground) {
    exec (nombre, proceso, enBackground);
}

char *
getVideo (void) {
    PROCESO *temp;
    temp = GetProcessByPid (pidActual);

    if (temp->terminal == terminalActual)
        return (char *) 0xb8000;
    else
        return terminales[temp->terminal].terminal;
}

char *
getBuffer (void) {
    return keyboardBuffer;
}

void
int_09 (int scanCode) {
    int proxTerm, i;
    int code = scanCode & 0x00FF;
    char ascii = getAscii (code);

    if (canBeShowed (code)) {  
         /* Si el caracter se puede mostrar, se escribe en el buffer */
        for(i = 0; i < MAXPROCESOS; i++)
        {
            if(procesos[i].terminal == terminalActual 
                && !procesos[i].background && procesos[i].bloqueado == WAITING_INPUT)
            {
                procesos[i].bloqueado=0;
            }
        }
        addKeyToBuffer (ascii);
    }
    else if (isFmake (code)) {

        proxTerm = ascii - F1;

        if (proxTerm >= NTERMS)
            return;

        if (proxTerm == terminalActual)
            return;
        else {

            desbloqueaProceso (proxTerm + 1);
            if(!estaBloqueado(terminalActual + 1))
                bloqueaProceso (terminalActual + 1, 1);

            for (i = 0; i < FILAS * COLS * 2; i++) {
                backup[i] = terminales[terminalActual].terminal[i];
            }

            terminales[terminalActual].terminal = backup;
            terminales[terminalActual].posicion = tickpos;
            terminales[terminalActual].readBuf = readBuffer;
            terminales[terminalActual].writeBuf = writeBuffer;

            tickpos = terminales[proxTerm].posicion;
            backup = terminales[proxTerm].terminal;
            terminales[proxTerm].terminal = (char *) 0xb8000;
            update_cursor (tickpos / 160, (tickpos % 160) / 2);

            for (i = 0; i < FILAS * COLS * 2; i++) {
                terminales[proxTerm].terminal[i] = backup[i];
            }

            keyboardBuffer = terminales[proxTerm].buffer;
            writeBuffer = terminales[proxTerm].writeBuf;
            readBuffer = terminales[proxTerm].readBuf;
            terminalActual = proxTerm;
        }
    }
}

void
escribir (char *donde, char *desde, int cant) {
    int j;
    int offset;

    unsigned int *pos;
    PROCESO *temp;

    temp = GetProcessByPid (pidActual);
    if (temp->terminal == terminalActual) {
        pos = &tickpos;
    }
    else {
        pos = &terminales[temp->terminal].posicion;
    }

    if (*pos >= COLS * FILAS * 2) {
        scroll (donde, COLS, FILAS);

        /* llegue al final de la pantalla */
        for (j = COLS * FILAS * 2; j >= COLS * (FILAS - 1) * 2; j -= 2)
            donde[j] = 0;

        *pos = COLS * (FILAS - 1) * 2;
    }

    for (j = 0; j < cant; j++) {
        switch (desde[j]) {
        case '\n':
            /* hay que mover el cursor a la proxima linea */
            offset = MAXLINEA - (tickpos % MAXLINEA);
            *pos += offset;
            break;
        case '\t':
            /* tabs de 4 espacios */
            offset = 8 - *pos % 8;
            *pos += offset;
            break;
        case '\b':
            *pos -= 2;
            donde[*pos] = 0;
            break;
        default:
            donde[*pos] = desde[j];
            *pos += 2;
            break;
        }
    }

    if (terminalActual == temp->terminal)
        update_cursor (*pos / 160, (*pos % 160) / 2);
}

char *
int_86 (int codigo, void *param1, void *param2) {
    switch (codigo) {
        case READ_CODE:
            return (char *) GetChar();
            break;
        case MALLOC_CODE:
            return (char *) userMalloc((int) param1);
            break;
        case SHMGET_CODE:
            return (char *) shmget ((int) param1, (int) param2);
            break;
        case SHMAT_CODE:
            return (char *) shmat ((int) param1);
            break;
        case LOCK_CODE:
            return (char *) lockMyMem ((int) param1);
            break;
        case UNLOCK_CODE:
            return (char *) unlockMyMem ((int) param1);
            break;
        default:
            break;
    }
}

void
int_85 (int codigo, void *parametro1, void *parametro2, void *parametro3) {
    switch (codigo) {
        case EXEC_CODE:
            exec (parametro1, parametro2, (int) parametro3);
            break;
        case RENICE_CODE:
            Renice ((int) parametro1, (int) parametro2);
            break;
        case KILL_CODE:
            Kill ((int) parametro1);
            break;
        case WRITE_CODE:
            escribir ((char *) parametro1, (char *) parametro2, (int) parametro3);
            break;
        default:
            break;
    }
    return;
}


void
seteaIDT(void) {
    setup_IDT_entry (&idt[0x08], 0x08, (dword) & int_08_hand, ACS_INT, 0);
    // CARGA DE IDT CON LA RUTINA DE ATENCION DE TECLADO
    setup_IDT_entry (&idt[0x09], 0x08, (dword) & int_09_hand, ACS_INT, 0);
    // ints nuestras
    setup_IDT_entry (&idt[0x85], 0x08, (dword) & int_85_hand, ACS_INT, 0);
    setup_IDT_entry (&idt[0x86], 0x08, (dword) & int_86_hand, ACS_INT, 0);
    // divide by zero
    setup_IDT_entry (&idt[0x00], 0x08, (dword)&div0_hand, ACS_INT, 0);
    // out of bounds
    setup_IDT_entry (&idt[0x05], 0x08, (dword)&bounds_hand, ACS_INT, 0);
    // opcode invalido
    setup_IDT_entry (&idt[0x06], 0x08, (dword)&opCode_hand, ACS_INT, 0);    
    // segmento no presente
    setup_IDT_entry (&idt[0x0B], 0x08, (dword)&snoPresent_hand, ACS_INT, 0);
    // ay se me rompe el stack!
    setup_IDT_entry (&idt[0x0C], 0x08, (dword)&ssf_hand, ACS_INT, 0);   
    // error de prot. general
    setup_IDT_entry (&idt[0x0D], 0x08, (dword)&generalPfault_hand, ACS_INT, 0); 
    // saca la mano antonio de mi pagina
    setup_IDT_entry (&idt[0x0E], 0x08, (dword)&pageFault_hand, ACS_INT, 0); 

    /* Carga de IDTR    */
    idtr.base = 0;
    idtr.base += (dword) & idt;
    idtr.limit = sizeof (idt) - 1;

    _lidt (&idtr);
}

void
initPaging(void) {
    unsigned long address = 0;  // holds the physical address of where a page is
    unsigned int i, j;

 // map the kernel pages and table directories and table pages zone (first 8MB)
    for (i = 0; i < 2; i++) {
        page_table = page_table + 4096;

        for (j = 0; j < 1024; j++) {
            // fill  the page directory
            page_table[j] = address | 3;
         // attribute set to: supervisor level, read/write, present(011 in binary)
            address = address + 4096;
        }

        page_directory[i] = (unsigned long)page_table;
        page_directory[i] = page_directory[i] | 3;

    }

    // map the rest of memory left with no present attribute
    page_table = page_directory + 8192;

    for (i = 2; i < (int)/*maxmem/4096*/8 ; i++) {
        /*Dado que cada entrada de la tabla de paginas son de 32 bits, me corro 
        1024 lugares que fueron usados para escribir las siguientes entradas */
        page_table =  page_table + 4096;

        for (j = 0; j < 1024; j++) {

            page_table[j] = address & 0xFFFFFFFE;
            address = address + 4096;
        }

        page_directory[i] = (unsigned long) page_table;
        page_directory[i] = page_directory[i] & 0xFFFFFFFE ;


    }
    // page var reload his initial value

   // write_cr3, read_cr3, write_cr0, and read_cr0 all come from the 
    // assembly functions
    write_cr3 (page_directory); // put that page directory address into CR3
    write_cr0 (read_cr0 () | 0x80000000);   // set the paging bit in CR0 to 1
}

/**********************************************
kmain() 
Punto de entrada de codigo C.
*************************************************/

kmain (unsigned long infogrub, unsigned int magic) {
    int num, j, k;

    _Cli ();

    /* Borra la pantalla. */
    k_clear_screen ();

    seteaIDT();

    /* Habilito interrupcion de timer tick , teclado y COM */
    mascaraPIC1 (IRQ0 & IRQ1 & IRQ3 & IRQ4);
    mascaraPIC2 (0xFF);

    set_multiBoot (infogrub);
    /* cant. de memoria existente en kbytes */
    maxmem = getMemSize ()+1024;

    initPaging ();
    initScheduler ();

    for (k = 0; k < NTERMS; k++) {
        terminales[k].terminal = CallocTerm (FILAS * COLS * 2);
        terminales[k].posicion = 0;
        terminales[k].writeBuf = terminales[k].buffer;
        terminales[k].readBuf = terminales[k].buffer;

        for (j = 0; j < COLS * 2; j++)
            terminales[k].buffer[j] = 0;
    }

    backup = terminales[0].terminal;
    keyboardBuffer = terminales[0].buffer;
    writeBuffer = terminales[0].writeBuf;
    readBuffer = terminales[0].readBuf;
    terminales[0].terminal = (char *) 0xb8000;

    // inicia un proceso shell en cada tty y a dios.
    CrearProceso ("shell 0", shell, 0, 0, (char **) 0, 2, 0, 0x4000);
    CrearProceso ("shell 1", shell, 1, 0, (char **) 0, 2, 0, 0x4000);
    CrearProceso ("shell 2", shell, 2, 0, (char **) 0, 2, 0, 0x4000);
    CrearProceso ("shell 3", shell, 3, 0, (char **) 0, 2, 0, 0x4000);
    CrearProceso ("shell 4", shell, 4, 0, (char **) 0, 2, 0, 0x4000);
    CrearProceso ("shell 5", shell, 5, 0, (char **) 0, 2, 0, 0x4000);
    CrearProceso ("GOD", godswork, 0, 0, (char **)0, 2, 0, 0x4000);

    /* Solo la tty 0 inicia como no bloqueada. */
    for (k = 2; k <= NTERMS; k++) {
        bloqueaProceso (k, 1);
    }
    // pero god es god.
    desbloqueaProceso(GOD);
    _Sti ();

    while (1) {
    }

}
